@x12i/editor 0.0.0-stage → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +361 -3
- package/dist/chunk-FYXMOAJT.js +1890 -0
- package/dist/chunk-FYXMOAJT.js.map +1 -0
- package/dist/index.d.ts +220 -0
- package/dist/index.js +1326 -0
- package/dist/index.js.map +1 -0
- package/dist/outline-Drc3E0C1.d.ts +339 -0
- package/dist/react.d.ts +546 -0
- package/dist/react.js +4439 -0
- package/dist/react.js.map +1 -0
- package/dist/styles.css +1219 -0
- package/package.json +88 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 x12i
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,361 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# @x12i/editor
|
|
2
|
+
|
|
3
|
+
An accessible block editor for the Unified UI Kit, built on [Tiptap](https://tiptap.dev) v3 (MIT).
|
|
4
|
+
|
|
5
|
+
The stored document is **JSON that the kit owns** (`EditorDocument`, versioned). Tiptap is only the
|
|
6
|
+
engine that edits it: the editor converts to and from Tiptap/ProseMirror JSON at its edges, so the
|
|
7
|
+
engine can be replaced without migrating saved documents.
|
|
8
|
+
|
|
9
|
+
- Headings, lists, task lists, tables, callouts, toggles, columns, code, equations, images, video, audio,
|
|
10
|
+
files and bookmarks, plus the kit blocks: **chart**, **diagram**, **data view** and **AI block**.
|
|
11
|
+
- Slash menu, markdown shortcuts, floating selection menu, toolbar, block menu, keyboard block moves.
|
|
12
|
+
- Read-only `EditorView` that renders on the server.
|
|
13
|
+
- Headless converters and exporters (HTML, Markdown, plain text, outline, diff, validation) that run anywhere.
|
|
14
|
+
- Provider contracts for AI assistance (with a ready streaming UI) and collaboration (contract only).
|
|
15
|
+
- Tokens-only styling (light, dark and high-contrast themes), RTL, i18n through a `messages` prop.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
pnpm add @x12i/editor @x12i/ui react react-dom
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`@x12i/ui` and React are peer dependencies. The editor imports `Button`, `IconButton`, `Badge`, `Stack`,
|
|
24
|
+
`Inline`, `Divider`, `Spinner` and `VisuallyHidden` from `@x12i/ui`. Wrap your app in the kit's
|
|
25
|
+
`ThemeProvider` (and optionally `DensityProvider`) and load the editor stylesheet once:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import "@x12i/editor/styles.css";
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Two entry points
|
|
32
|
+
|
|
33
|
+
| Import | Contents | Runs on the server |
|
|
34
|
+
| ---------------------------- | ------------------------------------------------------------------------------------------ | ------------------ |
|
|
35
|
+
| `@x12i/editor` | Document schema and types, converters, exporters, outline, diff, validation, token helpers | Yes. No React, DOM or Tiptap imports. |
|
|
36
|
+
| `@x12i/editor/react` | `Editor`, `EditorView`, `EditorToolbar`, AI menu and hook, message helpers | `EditorView` yes; `Editor` renders `EditorView` until it mounts. |
|
|
37
|
+
| `@x12i/editor/styles.css` | The one stylesheet | n/a |
|
|
38
|
+
|
|
39
|
+
`@x12i/editor/react` starts with `"use client"`, so it works from a Next.js server component tree as a
|
|
40
|
+
client boundary. The root entry has no directive and no side effects.
|
|
41
|
+
|
|
42
|
+
### Server-side rendering
|
|
43
|
+
|
|
44
|
+
- `EditorView` is a pure render of the document. Use it in server components, static pages and email-like
|
|
45
|
+
output, and wherever people only read.
|
|
46
|
+
- `Editor` creates its Tiptap instance with `immediatelyRender: false`: on the server and during hydration it
|
|
47
|
+
renders the same markup as `EditorView`, then swaps in the live editor after mount. No hydration mismatch,
|
|
48
|
+
no layout jump.
|
|
49
|
+
- `documentToHtml(doc)` returns an HTML string with no React at all (all text is escaped, URLs are checked).
|
|
50
|
+
|
|
51
|
+
## Quick start
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
"use client";
|
|
55
|
+
import { useState } from "react";
|
|
56
|
+
import { createEmptyDocument, type EditorDocument } from "@x12i/editor";
|
|
57
|
+
import { Editor } from "@x12i/editor/react";
|
|
58
|
+
import "@x12i/editor/styles.css";
|
|
59
|
+
|
|
60
|
+
export function Notes() {
|
|
61
|
+
const [doc, setDoc] = useState<EditorDocument>(() => createEmptyDocument());
|
|
62
|
+
return <Editor value={doc} onChange={setDoc} debounce={300} aria-label="Meeting notes" autoFocus />;
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`value` makes the editor controlled; `defaultValue` makes it uncontrolled. External changes to `value` are
|
|
67
|
+
applied only when the new document differs from what the editor holds, and never fire `onChange`.
|
|
68
|
+
|
|
69
|
+
### Read-only view
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import { EditorView } from "@x12i/editor/react";
|
|
73
|
+
|
|
74
|
+
<EditorView value={doc} blockRenderers={blockRenderers} idPrefix="post-12-" />;
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Imperative API
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
import { useRef } from "react";
|
|
81
|
+
import type { EditorApi } from "@x12i/editor/react";
|
|
82
|
+
|
|
83
|
+
const api = useRef<EditorApi>(null);
|
|
84
|
+
|
|
85
|
+
<Editor ref={api} defaultValue={doc} />;
|
|
86
|
+
|
|
87
|
+
api.current?.insertBlock("callout", { variant: "warning" });
|
|
88
|
+
api.current?.exec("heading2");
|
|
89
|
+
const markdown = api.current?.getMarkdown();
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`onReady(api)` hands you the same object. `EditorApi`: `getDocument`, `setDocument`, `focus`, `insertBlock`,
|
|
93
|
+
`exec`, `getMarkdown`, `getOutline`, `getEngine` (the raw Tiptap editor) and `announce`.
|
|
94
|
+
|
|
95
|
+
## Charts, diagrams and data views: `blockRenderers`
|
|
96
|
+
|
|
97
|
+
The editor **never imports** chart, diagram or data components. `@x12i/charts`, `@x12i/diagrams` and
|
|
98
|
+
`@x12i/data` are used for **types only**. Embedded blocks are drawn by renderers you inject, so an app pays
|
|
99
|
+
only for what it uses and the packages stay independent:
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
import type { BlockRenderers } from "@x12i/editor/react";
|
|
103
|
+
// Your app's own components: the editor does not care where they come from.
|
|
104
|
+
import { MyChart, MyDiagram, MyDataView } from "./blocks";
|
|
105
|
+
|
|
106
|
+
// Define at module level (or memoize): see "Renderer identity" below.
|
|
107
|
+
const blockRenderers: BlockRenderers = {
|
|
108
|
+
chart: ({ payload, title }) => <MyChart spec={payload} aria-label={title} />, // payload: ChartSpec
|
|
109
|
+
diagram: ({ payload }) => <MyDiagram engine={payload.engine} document={payload.diagram} source={payload.source} />,
|
|
110
|
+
"data-view": ({ payload }) => <MyDataView dataset={payload.dataset} view={payload.view} />, // payload.view: SavedView
|
|
111
|
+
};
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Each renderer receives `{ type, id, payload, title, editable, selected }`. Payload types are exported as
|
|
115
|
+
`EmbedPayloads`.
|
|
116
|
+
|
|
117
|
+
Without a renderer, a block shows a tidy default card: a titled group with a summary and a "Show data"
|
|
118
|
+
disclosure holding the JSON. That card is also what appears if a renderer throws (each renderer sits in an
|
|
119
|
+
error boundary).
|
|
120
|
+
|
|
121
|
+
### Editing and inserting embedded blocks
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
<Editor
|
|
125
|
+
blockRenderers={blockRenderers}
|
|
126
|
+
onEditBlock={async (type, payload) => openChartDialog(type, payload) /* resolve the new payload, or null */}
|
|
127
|
+
onRequestBlock={async (type) => pickChart(type) /* resolve a payload, or null to cancel */}
|
|
128
|
+
/>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Selecting an embedded block shows a small "Block tools" toolbar (Edit, Move up, Move down, Remove). Pressing
|
|
132
|
+
Enter on a selected block calls `onEditBlock`. Choosing "Chart" in the slash menu calls `onRequestBlock`; if
|
|
133
|
+
it is missing, the block is created with an empty payload and the default card.
|
|
134
|
+
|
|
135
|
+
### Renderer identity
|
|
136
|
+
|
|
137
|
+
Renderers are mounted as React components, so they may use hooks. Keep the functions stable (module level
|
|
138
|
+
or `useMemo`): a new function identity on each render makes React remount that block and discard its state.
|
|
139
|
+
|
|
140
|
+
## Supported blocks and marks
|
|
141
|
+
|
|
142
|
+
| Document block (`type`) | Tiptap node | Notes |
|
|
143
|
+
| --------------------------- | ----------------------------------- | ---------------------------------------------------------------------- |
|
|
144
|
+
| `paragraph` | `paragraph` | |
|
|
145
|
+
| `heading` (level 1 to 4) | `heading` | |
|
|
146
|
+
| `quote` | `blockquote` | |
|
|
147
|
+
| `callout` (`variant`) | `callout` (custom) | info, success, warning, danger, neutral. `role="note"`. |
|
|
148
|
+
| `toggle` | `toggle` + `toggleSummary` (custom) | Disclosure button; Alt+Enter toggles. |
|
|
149
|
+
| `divider` | `horizontalRule` | |
|
|
150
|
+
| `bulleted-list` | `bulletList` / `listItem` | Nested. |
|
|
151
|
+
| `numbered-list` | `orderedList` / `listItem` | Nested. |
|
|
152
|
+
| `task-list` | `taskList` / `taskItem` | Nested. Each checkbox is labelled with its text. |
|
|
153
|
+
| `code` (`language`) | `codeBlock` (custom) | Language picker and copy button. No syntax highlighting. |
|
|
154
|
+
| `equation` | `equation` (custom) | LaTeX source shown as text. No math renderer bundled. |
|
|
155
|
+
| `table` | `table`, `tableRow`, `tableHeader`, `tableCell` | Row/column menu; header row and column; col/row spans kept. |
|
|
156
|
+
| `image` | `image` (custom view) | Alt text required (or "decorative"), caption, alignment, width. |
|
|
157
|
+
| `video`, `audio` | `media` (custom) | By URL; native controls. |
|
|
158
|
+
| `file` | `file` (custom) | Link card with name and size. |
|
|
159
|
+
| `bookmark` | `bookmark` (custom) | Link card. |
|
|
160
|
+
| `columns` | `columns` / `column` (custom) | |
|
|
161
|
+
| `page-break` | `pageBreak` (custom) | Breaks the page when printing. |
|
|
162
|
+
| `table-of-contents` | `tableOfContents` (custom) | Live list of headings. |
|
|
163
|
+
| `chart` | `chart` (atom, injected renderer) | Payload is a `ChartSpec`. |
|
|
164
|
+
| `diagram` | `diagram` (atom, injected renderer) | `engine` + `DiagramDocument` or `source`. |
|
|
165
|
+
| `data-view` | `dataView` (atom, injected renderer)| `dataset` + `SavedView`. |
|
|
166
|
+
| `ai` | `aiBlock` (atom, injected renderer) | `prompt` + optional `result` blocks. |
|
|
167
|
+
| any other `type` | `unknownBlock` | Kept untouched, reported as an `unknown-block` warning. |
|
|
168
|
+
|
|
169
|
+
| Inline | Tiptap | Notes |
|
|
170
|
+
| --------------------- | ------------------------------ | --------------------------------------------------------------------- |
|
|
171
|
+
| `bold`, `italic`, `strike`, `code` | `bold`, `italic`, `strike`, `code` | |
|
|
172
|
+
| `underline` | `underline` | |
|
|
173
|
+
| `link` (`href`) | `link` | URL allow-list: see Security. |
|
|
174
|
+
| `color` (`token`) | `textColor` (custom) | Stores a token path, never a color value. |
|
|
175
|
+
| `highlight` (`token`) | `highlight` | Stores a token path, never a color value. |
|
|
176
|
+
| `mention` | `mention` (custom) | Existing mentions render; there is no picker. |
|
|
177
|
+
| `emoji` (`shortcode`) | `emoji` (custom) | Stored as a shortcode, rendered as the character with an accessible name. |
|
|
178
|
+
|
|
179
|
+
## Markdown shortcuts
|
|
180
|
+
|
|
181
|
+
Typed at the start of a block (then continue typing):
|
|
182
|
+
|
|
183
|
+
| Type | Result |
|
|
184
|
+
| ----------------------------- | ------------------------- |
|
|
185
|
+
| `# `, `## `, `### `, `#### ` | Heading 1 to 4 |
|
|
186
|
+
| `- `, `* `, `+ ` | Bulleted list |
|
|
187
|
+
| `1. ` | Numbered list |
|
|
188
|
+
| `[ ] `, `[x] ` | Task list (unchecked, checked) |
|
|
189
|
+
| `> ` | Quote |
|
|
190
|
+
| ```` ```ts ````, `~~~` | Code block (language optional) |
|
|
191
|
+
| `---` | Divider |
|
|
192
|
+
|
|
193
|
+
Inline: `**bold**`, `*italic*` or `_italic_`, `~~strike~~`, `` `code` ``, and `:rocket:` for emoji.
|
|
194
|
+
Pasted text keeps its formatting where Tiptap can parse it; URLs auto-link; unsafe URLs never become links.
|
|
195
|
+
|
|
196
|
+
## Keyboard
|
|
197
|
+
|
|
198
|
+
Modifier is Ctrl on Windows and Linux, Cmd on macOS.
|
|
199
|
+
|
|
200
|
+
| Keys | Action |
|
|
201
|
+
| ------------------------------ | --------------------------------------------------------------------- |
|
|
202
|
+
| `/` at the start of a block or after a space | Open the slash menu |
|
|
203
|
+
| `ArrowDown` / `ArrowUp` (slash menu) | Move through blocks (wraps) |
|
|
204
|
+
| `Enter` or `Tab` (slash menu) | Insert the highlighted block |
|
|
205
|
+
| `Escape` (slash menu) | Close the menu and keep what you typed |
|
|
206
|
+
| `Mod+/` | Open the block menu (turn into, duplicate, delete, move, add below) |
|
|
207
|
+
| `Alt+Shift+Up` / `Down` | Move the current block up or down (announced to screen readers) |
|
|
208
|
+
| `Mod+K` | Add or edit a link |
|
|
209
|
+
| `Mod+B`, `Mod+I`, `Mod+U` | Bold, italic, underline |
|
|
210
|
+
| `Mod+Shift+S`, `Mod+E` | Strikethrough, inline code |
|
|
211
|
+
| `Mod+Shift+7` / `8` / `9` | Numbered, bulleted, task list |
|
|
212
|
+
| `Mod+Shift+B` | Quote |
|
|
213
|
+
| `Mod+Alt+C` | Toggle code block |
|
|
214
|
+
| `Mod+Enter` (code block) | Leave the code block |
|
|
215
|
+
| `Tab` / `Shift+Tab` in a list | Indent / outdent the item when that is possible; otherwise focus moves on. |
|
|
216
|
+
| `Tab` / `Shift+Tab` in a table | Next / previous cell. `Tab` in the last cell adds a row; `Shift+Tab` in the first cell moves focus out. |
|
|
217
|
+
| `Alt+Enter` (in a toggle) | Expand or collapse |
|
|
218
|
+
| `Enter` (embedded block selected) | Edit the block (`onEditBlock`) |
|
|
219
|
+
| `Mod+Z`, `Mod+Shift+Z` | Undo, redo |
|
|
220
|
+
| `Tab` to the toolbar, then `Left` / `Right`, `Home`, `End` | Toolbar is one tab stop with roving focus |
|
|
221
|
+
| `Escape` (menus, dialogs, bubble menu, AI menu) | Close and return to the text |
|
|
222
|
+
|
|
223
|
+
## Accessibility
|
|
224
|
+
|
|
225
|
+
The editor is built to the kit's accessibility rules (spec section 13) and tested with
|
|
226
|
+
[axe](https://github.com/dequelabs/axe-core) in every tested state: editor with all blocks, toolbar, open
|
|
227
|
+
menus, slash listbox, dialogs, embedded blocks, and the read-only view.
|
|
228
|
+
|
|
229
|
+
- The editable area is `role="textbox"` with `aria-multiline="true"` and an accessible name
|
|
230
|
+
(`aria-label` / `aria-labelledby`, or the default from `messages`). The read-only editor is `role="document"`.
|
|
231
|
+
- Not a keyboard trap in text: `Tab` moves focus out of the editor (it is only used to indent list items, move
|
|
232
|
+
between table cells and confirm a slash menu choice), and `Escape` always closes the open menu. In a table,
|
|
233
|
+
`Tab` in the last cell adds a row (the Tiptap default); leave a table with `Shift+Tab` from its first cell or
|
|
234
|
+
with the arrow keys, then `Tab` from the text after it.
|
|
235
|
+
- The toolbar is a `role="toolbar"` with a roving tabindex (one tab stop, arrow keys inside). Toggle buttons
|
|
236
|
+
expose `aria-pressed`; disabled states are real `disabled`.
|
|
237
|
+
- The slash menu is a `listbox`: the editor carries `aria-controls` and `aria-activedescendant` while it is
|
|
238
|
+
open, the options carry `aria-selected`, and result counts are announced through a polite live region.
|
|
239
|
+
- Block moves, formatting changes, links, images, copy and AI results are announced in a status region.
|
|
240
|
+
- Dialogs (link, image) trap focus, close on `Escape`, return focus to where they started, and validate with
|
|
241
|
+
`aria-invalid` + `aria-describedby` error text. Images require alternative text, or an explicit "decorative".
|
|
242
|
+
- Focus rings use the kit's `--kit-color-border-focus` and `--kit-border-width-focus` tokens and are never
|
|
243
|
+
removed. Windows forced-colors and `prefers-reduced-motion` are handled in the stylesheet.
|
|
244
|
+
- Color is never the only signal: callouts have a variant label, task state is a checkbox, and the text colors
|
|
245
|
+
and highlights on the allow-lists are chosen from the kit's contrast-checked foreground/background tokens.
|
|
246
|
+
- RTL: only logical CSS properties; block elements use `dir="auto"` so right-to-left text mirrors lists, quotes
|
|
247
|
+
and tables. Code and equations are always `dir="ltr"`.
|
|
248
|
+
|
|
249
|
+
## Theming
|
|
250
|
+
|
|
251
|
+
All styling uses `--kit-*` design tokens, nothing is hard-coded. Every selector in `styles.css` is wrapped in
|
|
252
|
+
`:where()` so it has zero specificity and is easy to override. Hooks for your own CSS:
|
|
253
|
+
`[data-kit="editor"]`, `editor-toolbar`, `editor-content`, `editor-callout` (`data-variant`), `editor-toggle`
|
|
254
|
+
(`data-open`), `editor-task-item` (`data-checked`), `editor-embed-block` (`data-embed`, `data-selected`),
|
|
255
|
+
`editor-embed-card`, `editor-slash`, `editor-slash-option`, `editor-bubble`, `editor-dialog-form`, and so on. Dark mode and high contrast follow `ThemeProvider`. The editor
|
|
256
|
+
defines only two private custom properties (`--kit-editor-x`, `--kit-editor-y`) for menu positioning.
|
|
257
|
+
|
|
258
|
+
## Internationalization
|
|
259
|
+
|
|
260
|
+
Every string comes from `messages`. Pass a partial override; missing keys fall back to English:
|
|
261
|
+
|
|
262
|
+
```tsx
|
|
263
|
+
import { Editor, defaultMessages, type DeepPartial, type Messages } from "@x12i/editor/react";
|
|
264
|
+
|
|
265
|
+
const de: DeepPartial<Messages> = {
|
|
266
|
+
placeholder: "Schreibe etwas oder drücke / für Blöcke",
|
|
267
|
+
toolbar: { bold: "Fett", italic: "Kursiv" },
|
|
268
|
+
blocks: { heading1: { name: "Überschrift 1" } },
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
<Editor messages={de} locale="de" />;
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Templates use ICU-style placeholders and `{count, plural, one {...} other {...}}` (via `Intl.PluralRules`
|
|
275
|
+
with `locale`). Slash menu search ignores case and diacritics and matches names, descriptions and keywords in
|
|
276
|
+
the active language. Helpers: `defaultMessages`, `mergeMessages`, `createTranslator`, `formatMessage`,
|
|
277
|
+
`useMessages`, `useTranslator`.
|
|
278
|
+
|
|
279
|
+
## AI assistance
|
|
280
|
+
|
|
281
|
+
The kit calls no model. Provide an `AiProvider` (from the root entry) that streams text:
|
|
282
|
+
|
|
283
|
+
```tsx
|
|
284
|
+
import type { AiProvider } from "@x12i/editor";
|
|
285
|
+
|
|
286
|
+
const aiProvider: AiProvider = {
|
|
287
|
+
async *run({ command, selection, tone, signal }) {
|
|
288
|
+
const response = await fetch("/api/ai", { method: "POST", body: JSON.stringify({ command, selection, tone }), signal });
|
|
289
|
+
if (!response.ok || !response.body) throw new Error(`Request failed (${response.status})`);
|
|
290
|
+
const decoder = new TextDecoder();
|
|
291
|
+
for await (const chunk of response.body) yield decoder.decode(chunk, { stream: true });
|
|
292
|
+
},
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
<Editor aiProvider={aiProvider} />;
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
With a provider, the toolbar and the selection menu show an "AI assistant" menu (improve, shorten, continue,
|
|
299
|
+
fix spelling and grammar, ask with an instruction). The result streams into a **preview**; nothing changes the
|
|
300
|
+
document until the person chooses Accept, and Stop or Reject discards it. Build your own surface with
|
|
301
|
+
`useAiAssist({ provider, editor })` (status, preview, `run`, `accept`, `reject`, `cancel`) or `AiAssistMenu`.
|
|
302
|
+
|
|
303
|
+
## Headless API (root entry)
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
import {
|
|
307
|
+
documentToTiptap, tiptapToDocument, // convert to and from the engine's JSON
|
|
308
|
+
documentToHtml, documentToMarkdown, documentToPlainText,
|
|
309
|
+
markdownToDocument, // import (GFM subset)
|
|
310
|
+
getOutline, buildOutlineTree, // headings, ids, anchors
|
|
311
|
+
diffDocuments, // which blocks were added, removed, changed
|
|
312
|
+
validateDocument, sanitizeDocument, isEditorDocument,
|
|
313
|
+
isSafeUrl, sanitizeUrl, sanitizeMediaUrl,
|
|
314
|
+
textColorToCss, highlightToCss, TEXT_COLOR_TOKENS, HIGHLIGHT_TOKENS,
|
|
315
|
+
} from "@x12i/editor";
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
All pure functions with typed options and warnings (`ConversionWarning`), safe to use in server code, workers
|
|
319
|
+
and tests. `tiptapToDocument(documentToTiptap(doc))` returns an equal document for every block and mark.
|
|
320
|
+
|
|
321
|
+
## Security
|
|
322
|
+
|
|
323
|
+
- **URLs.** Links allow `http`, `https`, `mailto`, `tel` and relative paths. `javascript:`, `data:`, `vbscript:`
|
|
324
|
+
and friends are rejected in the editor, the converters, the view and the exporters. Only the `src` of
|
|
325
|
+
`image`, `video` and `audio` blocks additionally accepts `data:image/*` (so pasted or inline images work);
|
|
326
|
+
links, files and bookmarks never accept `data:`. Whitespace, control and invisible characters are removed
|
|
327
|
+
before the scheme is checked, so `java<TAB>script:` is rejected too. Links get `rel="noopener noreferrer"`.
|
|
328
|
+
- **No raw styles.** Text color and highlight store a token path. Only tokens on the allow-lists in the
|
|
329
|
+
package render, as `var(--kit-...)`. A stored document cannot inject CSS.
|
|
330
|
+
- **No raw HTML.** The React components never use `dangerouslySetInnerHTML`. `documentToHtml` escapes all text
|
|
331
|
+
and attributes and emits no inline styles or event handlers, so its output is safe to inject.
|
|
332
|
+
- **Embedded renderers** are your code; the editor passes them only typed payloads, and catches their errors.
|
|
333
|
+
- **Unknown data** (a newer block type, an unknown mark) is preserved and reported, never executed.
|
|
334
|
+
- `sanitizeDocument` and `validateDocument` are there for documents from untrusted sources.
|
|
335
|
+
|
|
336
|
+
## Limitations
|
|
337
|
+
|
|
338
|
+
This is a complete editor, but not every feature of a large product. Honest list:
|
|
339
|
+
|
|
340
|
+
- **Collaboration is a contract only.** `CollaborationProvider` is typed; there is no Yjs binding, presence or
|
|
341
|
+
remote cursors in this version.
|
|
342
|
+
- **No drag handle.** Blocks move by keyboard (Alt+Shift+Up/Down), the block menu and the embed toolbar.
|
|
343
|
+
- **No syntax highlighting** in code blocks (language is stored and shown, not colored).
|
|
344
|
+
- **No mention picker.** Existing mentions render and round-trip; typing `@` does nothing yet.
|
|
345
|
+
- **No math renderer.** Equations show their LaTeX source in a monospace block.
|
|
346
|
+
- **Images by URL only.** No upload, drag-and-drop or paste-to-upload; `data:image` URLs are accepted.
|
|
347
|
+
- **Tables:** no column resizing. Cell content is inline only in the stored format. Merged cells are
|
|
348
|
+
preserved (`colSpan`, `rowSpan`).
|
|
349
|
+
- **Toggles open expanded** when a document loads; their open state is not stored.
|
|
350
|
+
- **Slash menu queries cannot contain spaces:** typing a space closes the menu.
|
|
351
|
+
- **Markdown export is lossy** for blocks Markdown cannot express (callout, toggle, columns, embeds are written
|
|
352
|
+
as readable fallbacks), and import follows GFM, where the first table row is the header.
|
|
353
|
+
- `trailingNode` is disabled, so a document may end with a non-paragraph block such as a table or a divider.
|
|
354
|
+
Add a line with "Add paragraph below" in the block menu (`Mod+/`).
|
|
355
|
+
- Extra `extensions` are read once, when the editor mounts.
|
|
356
|
+
- Code block, `Document`, toggle and callout nodes are written in this package because Tiptap's code-block
|
|
357
|
+
package is not a direct dependency under strict pnpm resolution.
|
|
358
|
+
|
|
359
|
+
## Licensing
|
|
360
|
+
|
|
361
|
+
MIT. Only MIT-licensed Tiptap packages are used; no Tiptap Pro, cloud or paid extensions.
|